package com.moose.operator.aop;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.RateLimiter;
import com.moose.operator.annotation.Limiter;
import com.moose.operator.exception.BusinessException;
import com.moose.operator.model.api.ResultCode;
import com.moose.operator.util.IpUtils;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;

/**
 * @author taohua
 */

@Aspect
@Configuration
public class LimiterAspect {

  /**
   * mock
   * <p>
   * 根据 IP 分不同的令牌桶, 每天自动清理缓存
   */
  private static LoadingCache<String, RateLimiter> caches = CacheBuilder.newBuilder()
      .maximumSize(1000)
      .expireAfterWrite(1, TimeUnit.DAYS)
      .build(new CacheLoader<String, RateLimiter>() {
        @Override
        public RateLimiter load(String key) {
          // 新的IP初始化 每秒只发出5个令牌
          return RateLimiter.create(5);
        }
      });

  @Pointcut("@annotation(com.moose.operator.annotation.Limiter)")
  public void limiterAspect() {

  }

  @Around("limiterAspect()")
  public Object around(ProceedingJoinPoint joinPoint) {
    MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    Method method = signature.getMethod();
    Limiter limitAnnotation = method.getAnnotation(Limiter.class);
    Limiter.LimitType limitType = limitAnnotation.limitType();
    String key = limitAnnotation.key();
    Object obj;
    try {
      if (limitType.equals(Limiter.LimitType.IP)) {
        key = IpUtils.getIpAddress();
      }
      RateLimiter rateLimiter = caches.get(key);
      Boolean flag = rateLimiter.tryAcquire();
      if (flag) {
        obj = joinPoint.proceed();
      } else {
        throw new BusinessException(ResultCode.OPERATION_LIMIT_FAIL);
      }
    } catch (Throwable e) {
      throw new BusinessException(ResultCode.OPERATION_LIMIT_FAIL);
    }
    return obj;
  }
}